﻿
#include "VSnoWarning.h"
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#include <windows.h>
#include <ctime>
#include <vector>
#include <string>
using namespace std;

#define DEFAULT_PORT 5019
#define CONNECTION_NUM 1024
#define RECV_BUFF_SIZE 1024
#define SEND_BUFF_SIZE 1024

string getCurrTime()
{
    char timeStr[11];
    time_t now;
    time(&now);
    tm* p = localtime(&now);
    sprintf(timeStr, "[%02d:%02d:%02d]", p->tm_hour, p->tm_min, p->tm_sec);
    return timeStr;
}

SOCKADDR_IN cAddr = { 0 };
int len = sizeof cAddr;
SOCKET clientSocket[CONNECTION_NUM];

int connectCount = 0;

struct Connection {
    char addr[15];
    int port;
    bool online;
    char username[100];  // Store username
};
struct Connection connections[CONNECTION_NUM];

struct Group {
    string groupName;
    vector<int> members;
};

vector<Group> groups;

void displayConn() {
    cout << getCurrTime() << " Current Connections:" << endl;
    for (int i = 0; i < connectCount; i++) {
        cout << connections[i].username << "@" << connections[i].addr << ":" << connections[i].port << " online: " << (connections[i].online ? "true" : "false") << endl;
    }
}

void sendMessageToGroup(const string& groupName, const string& message, const string& sender) {
    for (auto& group : groups) {
        if (group.groupName == groupName) {
            string fullMessage = sender + " (group " + groupName + "): " + message;
            for (int memberIndex : group.members) {
                if (connections[memberIndex].online) {
                    send(clientSocket[memberIndex], fullMessage.c_str(), fullMessage.length(), 0);
                }
            }
        }
    }
}

DWORD WINAPI commThread(LPVOID lpParam) {
    int index = (int)(intptr_t)lpParam;

    char recvBuff[RECV_BUFF_SIZE];
    char sendBuff[SEND_BUFF_SIZE];
    int r;

    // First, receive the username
    int nameLen = recv(clientSocket[index], connections[index].username, 100, 0);
    if (nameLen > 0) {
        connections[index].username[nameLen] = '\0'; // Ensure null-terminated string
        cout << getCurrTime() << connections[index].addr << ":" << connections[index].port << " input username as " << connections[index].username << endl;

        //send welcome msg to new connected client
        sprintf(sendBuff, "\nWelcome %s to chatroom, enter /help for manual\n", connections[index].username);
        send(clientSocket[index], sendBuff, strlen(sendBuff), 0);

        //announce others the username of the new connected client
        sprintf(sendBuff, "%s:%d's username is %s\n", connections[index].addr, connections[index].port, connections[index].username);
        for (int i = 0; i < connectCount; i++) {
            if (i != index) {
                send(clientSocket[i], sendBuff, strlen(sendBuff), 0);
            }
        }
    }

    // 7.Communication
    while (1) {
        r = recv(clientSocket[index], recvBuff, RECV_BUFF_SIZE - 1, 0);
        if (r > 0) {
            recvBuff[r] = '\0';
            bool quitFlag = false;
            memset(sendBuff, 0, SEND_BUFF_SIZE);

            // Extract the first word to see if it's a command
            string command(recvBuff);
            string firstWord = command.substr(0, command.find(' '));
            string restOfMessage = command.substr(command.find(' ') + 1);

            if (firstWord == "/quit") { // client quit
                cout << getCurrTime() << " " << connections[index].username << "(" << connections[index].addr << ":" << connections[index].port << ") quit" << endl;
                sprintf(sendBuff, "%s %s quit", getCurrTime().c_str(), connections[index].username);

                //bordcast to all users
                for (int i = 0; i < connectCount; i++) {
                    send(clientSocket[i], sendBuff, strlen(sendBuff), 0);
                }

                // Set record offline
                connections[index].online = false;
                quitFlag = true;
            }
            else if (firstWord == "/help") {//manual
                sprintf(sendBuff, "[user manual]\nEnter message '@username <message>' for private chat, just '<message>' for public chat\nEnter /quit to leave chatroom\nEnter /listonline to display all current online users\nEnter /kick to kick users (admin only)\nEnter /creategroup <groupname> <user1> <user2> ... to create a group chat\nEnter /sendgroup <groupname> <message> to send a message to a group");
                // Send back to client
                send(clientSocket[index], sendBuff, strlen(sendBuff), 0);
            }
            else if (firstWord == "/listonline") { // list online users
                sprintf(sendBuff, "List of online users:\n");
                int j = 1;
                for (int i = 0; i < connectCount; i++) {
                    if (connections[i].online) {
                        sprintf(sendBuff + strlen(sendBuff), "[%d] %s @ %s:%d\n", j++, connections[i].username, connections[i].addr, connections[i].port);
                    }
                }
                // Send back to client
                send(clientSocket[index], sendBuff, strlen(sendBuff), 0);
            }
            else if (firstWord == "/kick") {
                // admin permission
                if (!strcmp(connections[index].username, "admin")) {
                    string targetUsername = restOfMessage;
                    bool found = false;
                    for (int i = 0; i < connectCount; i++) {
                        if (connections[i].online && strcmp(connections[i].username, targetUsername.c_str()) == 0) {

                            //to target user
                            sprintf(sendBuff, "kicked: You are kicked by admin!");
                            send(clientSocket[i], sendBuff, strlen(sendBuff), 0);

                            //to sender
                            sprintf(sendBuff, "%s kicked!", connections[i].username);
                            send(clientSocket[index], sendBuff, strlen(sendBuff), 0);
                            found = true;
                            break;
                        }
                    }
                    if (!found) {
                        sprintf(sendBuff, "User %s not found or offline.", targetUsername.c_str());
                        send(clientSocket[index], sendBuff, strlen(sendBuff), 0);
                    }
                }
                else {
                    //to sender
                    sprintf(sendBuff, "Cannot kick users! You don't have an admin permission!");
                    send(clientSocket[index], sendBuff, strlen(sendBuff), 0);
                }
            }
            else if (firstWord == "/creategroup") { // Create group
                string groupName = restOfMessage.substr(0, restOfMessage.find(' '));
                string membersList = restOfMessage.substr(restOfMessage.find(' ') + 1);
                vector<int> members;
                bool validMembers = false;
                for (int i = 0; i < connectCount; i++) {
                    size_t pos = membersList.find(connections[i].username);
                    if (connections[i].online && pos != string::npos) {
                        members.push_back(i);
                        validMembers = true;
                    }
                }
                if (validMembers) {
                    Group newGroup = { groupName, members };
                    groups.push_back(newGroup);
                    sprintf(sendBuff, "Group %s created successfully.", groupName.c_str());
                }
                else {
                    sprintf(sendBuff, "No valid members found for group %s.", groupName.c_str());
                }
                send(clientSocket[index], sendBuff, strlen(sendBuff), 0);
            }
            else if (firstWord == "/sendgroup") { // Send group message
                string groupName = restOfMessage.substr(0, restOfMessage.find(' '));
                string groupMessage = restOfMessage.substr(restOfMessage.find(' ') + 1);
                sendMessageToGroup(groupName, groupMessage, connections[index].username);
            }
            else if (firstWord[0] == '@') { // Private message to a specific user
                string targetUsername = firstWord.substr(1); // Skip '@'
                bool found = false;
                for (int i = 0; i < connectCount; i++) {
                    if (connections[i].online && strcmp(connections[i].username, targetUsername.c_str()) == 0) {

                        //to target user
                        sprintf(sendBuff, "%s %s (private): %s", getCurrTime().c_str(), connections[index].username, restOfMessage.c_str());
                        send(clientSocket[i], sendBuff, strlen(sendBuff), 0);

                        //to sender
                        sprintf(sendBuff, "%s to %s (private): %s", getCurrTime().c_str(), connections[i].username, restOfMessage.c_str());
                        send(clientSocket[index], sendBuff, strlen(sendBuff), 0);
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    sprintf(sendBuff, "User %s not found or offline.", targetUsername.c_str());
                    send(clientSocket[index], sendBuff, strlen(sendBuff), 0);
                }
            }
            else { // Broadcast to all connected users
                cout << getCurrTime() << " " << connections[index].username << "(" << connections[index].addr << ":" << connections[index].port << ") says: " << recvBuff << endl;
                sprintf(sendBuff, "%s %s says: %s\n", getCurrTime().c_str(), connections[index].username, recvBuff);
                for (int i = 0; i < connectCount; i++) {
                    send(clientSocket[i], sendBuff, strlen(sendBuff), 0);
                }
            }

            // Exit thread if quit command was issued
            if (quitFlag) {
                break;
            }
        }
    }
    return 0;
}


int main() {
    WSADATA wsaData;
    int err = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (err != 0) {
        cout << "WSAStartup failed with error: " << err << endl;
        return 1;
    }
    else {
        cout << "WSAStartup success!" << endl;
    }

    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        cout << "Could not find a usable version of Winsock.dll" << endl;
        WSACleanup();
        return 1;
    }
    else {
        cout << "The Winsock 2.2 dll was found okay" << endl;
    }

    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serverSocket == SOCKET_ERROR) {
        cout << "Failed to create socket: " << GetLastError() << endl;
        WSACleanup();
        return -1;
    }
    else {
        cout << "Successfully created socket!" << endl;
    }

    SOCKADDR_IN addr = { 0 };
    addr.sin_family = AF_INET;
    addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(DEFAULT_PORT);

    int r = bind(serverSocket, (sockaddr*)&addr, sizeof addr);
    if (r == -1) {
        cout << "Bind failed: " << GetLastError() << endl;
        closesocket(serverSocket);
        WSACleanup();
        return -1;
    }
    else {
        cout << "Bind successful!" << endl;
    }

    r = listen(serverSocket, 10);
    if (r == -1) {
        cout << "Listen failed: " << GetLastError() << endl;
        closesocket(serverSocket);
        WSACleanup();
        return -1;
    }
    else {
        cout << "Listen successful!" << endl;
    }

    for (int i = 0; i < CONNECTION_NUM; i++) {
        clientSocket[i] = accept(serverSocket, (sockaddr*)&cAddr, &len);
        if (clientSocket[i] == SOCKET_ERROR) {
            cout << "Failed to connect client: " << GetLastError() << endl;
            closesocket(serverSocket);
            WSACleanup();
            return -1;
        }
        else {
            cout << getCurrTime() << " Successfully connected to client " << inet_ntoa(cAddr.sin_addr) << ":" << cAddr.sin_port << endl;
            connectCount++;

            connections[connectCount - 1].online = true;
            strcpy(connections[connectCount - 1].addr, inet_ntoa(cAddr.sin_addr));
            connections[connectCount - 1].port = ntohs(cAddr.sin_port);

            char sendBuff[SEND_BUFF_SIZE];
            memset(sendBuff, 0, SEND_BUFF_SIZE);
            sprintf(sendBuff, "%s %s:%d joined the chatroom", getCurrTime().c_str(), inet_ntoa(cAddr.sin_addr), cAddr.sin_port);
            for (int j = 0; j < connectCount; j++) {
                send(clientSocket[j], sendBuff, strlen(sendBuff), 0);
            }

            HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)commThread, (LPVOID)(intptr_t)i, 0, NULL);
            if (hThread) {
                CloseHandle(hThread);
            }
        }
    }

    closesocket(serverSocket);
    WSACleanup();
    cout << "Server socket closed" << endl;

    return 0;
}






